//
// FileRepo
//
// Copyright (C) jextra.net.
//
//  This file is part of the net.jextra.dep Library.
//
//  The net.jextra.dep Library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The net.jextra.dep Library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the net.jextra.dep Library; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.dep.repo;

import java.io.*;
import java.util.*;
import net.jextra.dep.*;
import net.jextra.dep.repo.version.*;

/**
 * <p>
 * Mounted drive, File-based {@link DepRepo}.
 * </p>
 */
public class FileRepo implements DepRepo
{
    // ============================================================
    // Fields
    // ============================================================

    public static final int BUFFER_SIZE = 10000;

    private String name;
    private File rootDir;
    private VersionOperator versionOperator;

    // ============================================================
    // Constructors
    // ============================================================

    public FileRepo( File rootDir )
        throws IOException
    {
        this( rootDir.getCanonicalPath(), rootDir );
    }

    public FileRepo( String name, File rootDir )
    {
        versionOperator = new DotVersionOperator();
        setRootDir( rootDir );
    }

    /**
     * Used only by a DepConfReader.
     */
    protected FileRepo()
    {
        versionOperator = new DotVersionOperator();
    }

    // ============================================================
    // Methods
    // ============================================================

    // ----------
    // public
    // ----------

    @Override
    public void init( DepRepoParam params )
        throws ClassNotFoundException, InstantiationException, IllegalAccessException
    {
        name = params.getAttribute( "name" );
        setRootDir( new File( params.getAttribute( "dir" ).trim() ) );
        if ( name == null )
        {
            name = rootDir.getAbsolutePath();
        }

        String versionOpString = params.getAttribute( "versionOperator" );
        if ( versionOpString != null )
        {
            versionOperator = (VersionOperator) ( Class.forName( versionOpString ).newInstance() );
        }
    }

    @Override
    public String getName()
    {
        return name;
    }

    @Override
    public int getTimeout()
    {
        return 0;
    }

    public VersionOperator getVersionOperator()
    {
        return versionOperator;
    }

    public File getRootDir()
        throws IOException
    {
        assert rootDir != null : "rootDir parameter can not be null.";
        assert rootDir.exists() : String.format( "rootDir [%s] does not exist.", rootDir.getCanonicalPath() );
        assert rootDir.isDirectory() : String.format( "rootDir [%s] must be a directory.", rootDir.getCanonicalPath() );

        return rootDir;
    }

    public void setRootDir( File rootDir )
    {
        if ( rootDir == null )
        {
            throw new DepException( "Store's root directory can not be null." );
        }

        if ( !rootDir.exists() )
        {
            throw new DepException( "Store's root directory '" + rootDir.getAbsolutePath() + "' does not exist." );
        }

        if ( !rootDir.isDirectory() )
        {
            throw new DepException( "Store's root directory '" + rootDir.getAbsolutePath() + "' is not a directory." );
        }

        this.rootDir = rootDir;
    }

    @Override
    public String getLatestVersionNumber( String componentName )
        throws Exception
    {
        List<DepRepoVersion> versions = getVersions( componentName );

        return versions == null || versions.isEmpty() ? null : versions.get( 0 ).getVersionNumber();
    }

    @Override
    public String getNextVersionNumber( String componentName )
        throws Exception
    {
        String latest = getLatestVersionNumber( componentName );
        return latest == null ? versionOperator.getStartingVersionString() : versionOperator
            .getNextVersionNumber( latest );
    }

    @Override
    public List<DepRepoVersion> getVersions( String componentName )
        throws Exception
    {
        assert componentName != null;

        HashMap<String, DepRepoVersion> versions = new HashMap<String, DepRepoVersion>();

        File componentDir = new File( getRootDir(), getPath( componentName ) );
        if ( !componentDir.exists() )
        {
            return new ArrayList<DepRepoVersion>();
        }

        for ( File dir : componentDir.listFiles() )
        {
            if ( dir.isDirectory() )
            {
                versions.put( dir.getName(),
                    new DepRepoVersion( this, componentName, dir.getName() ) );
            }
        }

        ArrayList<String> keys = new ArrayList<String>( versions.keySet() );
        Collections.sort( keys, Collections.reverseOrder( versionOperator ) );

        ArrayList<DepRepoVersion> list = new ArrayList<DepRepoVersion>();
        for ( String key : keys )
        {
            list.add( versions.get( key ) );
        }

        return list;
    }

    @Override
    public List<DepRepoFile> getFileEntries( String componentName, String versionNumber, String path )
        throws Exception
    {
        HashSet<File> files = new HashSet<File>();
        File versionDir = getComponentVersionDir( componentName, versionNumber );
        File dir = new File( versionDir, path );
        files.addAll( searchDir( dir ) );

        ArrayList<DepRepoFile> entries = new ArrayList<DepRepoFile>();
        for ( File file : files )
        {
            DepRepoFile entry = new DepRepoFile( this, componentName, versionNumber, getRelativePath( versionDir,
                file.getCanonicalFile() ), file.isDirectory() ? DepRepoFile.Type.directory : DepRepoFile.Type.file,
                file.lastModified(), file.length() );
            entries.add( entry );
        }

        Collections.sort( entries );

        return entries;
    }

    @Override
    public DepRepoFile getFileEntry( String componentName, String versionNumber, String path )
        throws Exception
    {
        File versionDir = getComponentVersionDir( componentName, versionNumber );
        File file = new File( versionDir, path );
        assert file.exists() : String.format( "entry %s/%s/%s does not exist.", componentName, versionNumber, path );

        DepRepoFile entry = new DepRepoFile( this, componentName, versionNumber, getRelativePath( versionDir,
            file.getCanonicalFile() ), file.isDirectory() ? DepRepoFile.Type.directory : DepRepoFile.Type.file,
            file.lastModified(), file.length() );

        return entry;
    }

    @Override
    public InputStream getFileEntryInputStream( String componentName, String versionNumber, String path )
        throws Exception
    {
        File file = getFile( componentName, versionNumber, path );

        return new FileInputStream( file );
    }

    @Override
    public String getComponentDisplayName( String componentName, String versionNumber )
        throws IOException
    {
        File dir = getComponentVersionDir( componentName, versionNumber );

        return dir.getCanonicalPath();
    }

    @Override
    public void uploadFile( String componentName, String versionNumber, String path, File file )
        throws Exception
    {
        assert path != null : "path parameter can not be null.";

        File versionDir = getComponentVersionDir( componentName, versionNumber );
        copyFile( file, new File( versionDir, path ) );
    }

    public File getComponentVersionDir( String componentName, String versionNumber )
    {
        File componentDir = new File( rootDir, getPath( componentName ) );
        File versionDir = new File( componentDir, versionNumber );
        return versionDir;
    }

    /**
     * Get a file in the store. It must already exist.
     */
    public File getFile( String componentName, String versionNumber, String path )
        throws IOException
    {
        File dir = getComponentVersionDir( componentName, versionNumber );
        File file = new File( dir, path );

        // Directories should not be returned!
        if ( file.isDirectory() )
        {
            return null;
        }

        return file.getCanonicalFile();
    }

    // ----------
    // private
    // ----------

    private static String getRelativePath( File rootDir, File file )
        throws IOException
    {
        String rootString = rootDir.getCanonicalPath().replace( '\\', '/' );
        while ( rootString.endsWith( "/" ) )
        {
            rootString = rootString.substring( 0, rootString.length() - 1 );
        }
        String path = file.getCanonicalPath().replace( '\\', '/' );
        assert path.startsWith( rootString ) : String.format( "%s is not part of the path %s", path, rootString );
        if ( path.length() == rootString.length() )
        {
            return "";
        }

        path = path.substring( rootString.length() + 1 );
        return path;
    }

    private Collection<File> searchDir( File repoDir )
        throws IOException
    {
        assert repoDir.exists() : String.format( "%s does not exist.", repoDir.getCanonicalPath() );
        assert repoDir.isDirectory() : String.format( "%s must be a directory.", repoDir.getCanonicalPath() );
        HashSet<File> files = new HashSet<File>();
        for ( File storeFile : repoDir.listFiles() )
        {
            files.add( storeFile );
        }
        return files;
    }

    private boolean copyFile( File from, File to )
        throws IOException
    {
        assert from != null : "From input stream can not be null.";
        assert to != null : "To file can not be null.";
        assert !to.isDirectory() : "To file can not be directory.";

        System.out.println( to.getParentFile().getAbsolutePath() );
        if ( !to.getParentFile().exists() )
        {
            boolean success = to.getParentFile().mkdirs();
            assert success : "Unable to create directory: " + to.getParentFile().getAbsolutePath();
        }

        System.out.printf( "Copying file [%s] to [%s].\n", from.getCanonicalPath(), to.getCanonicalPath() );
        FileInputStream in = new FileInputStream( from );
        FileOutputStream out = new FileOutputStream( to );
        byte[] buf = new byte[BUFFER_SIZE];
        int i = 0;
        while ( ( i = in.read( buf ) ) != -1 )
        {
            out.write( buf, 0, i );
        }
        in.close();
        out.close();
        return true;
    }
    
    private String getPath( String componentName )
    {
//        return componentName.replace( '.', '/' );
        return componentName;
    }
}
